library(tidyverse)
library(lme4)
library(emmeans)
library(car) # for vif
library(sjPlot)
library(broom)
library(knitr)
theme_set(ggthemes::theme_few())

Summary

Mixed modeling with all relevant variables predicting accuracy

From the preregistration, the mixed model was specified thusly:

correct ~ delay * age + 
          task_experience + cup_distance + board_size + trial +
          (1 + delay + trial | site/subject/block/hiding_location ) + 
          (1 + task_experience + cup_distance + board_size + trial + delay | species)

In the dataframe, subject_site = subject, and norm_age should be used for age.

Model as pre-registered has too many random effects

Error: number of observations (=6246) < number of random effects (=10608) for term (1 + delay + trial | hiding_location:(block:(subject_site:site))); the random-effects parameters are probably unidentifiable

Pruning random effects in the following order (from preregistration):

  • Remove correlations between random effects
  • Remove random slopes (in the following order)
    • species
    • hiding_location
    • block
    • subject

Model only converges once we take out hiding_location. After doing so, the other random effects (correlation, site, species) can be put back in.

The model below converges. Model output is saved in 06_mp_model_v2.rds

correct ~ delay * norm_age + 
          task_experience + cup_distance + board_size + trial + 
          (1 + delay + trial | site/subject_site/block) + 
          (1 + task_experience + cup_distance + board_size + trial + delay | species)

Reduced model

After pruning random effects with little variability and removing board_size, which covaried with cup_distance, the reduced model has the following structure. It is saved in 06_mp_3_model3_v2.rds

correct ~ delay * norm_age + 
          task_experience + cup_distance + trial + 
          (1 + delay | site/subject_site) + 
          (1 + delay | species)

Data prep

Data import

mp_data <- read.csv("../data/merged_data/01_manyprimates_pilot_merged_data_v2.csv")

Prepare code for pre-registered mixed modeling

  • center cup_distance, board_size and trial
  • filter out spider monkey. Only one data point so far, therefore this is not worth including to explode the number of random effects
model.data <- mp_data %>%
  filter(species != "black_faced_spider_monkey") %>%
  mutate_at(vars(cup_distance, board_size, trial), funs(scale(.)[,1])) %>%
  mutate(hiding_location = factor(hiding_location),
         delay = fct_relevel(delay, "short"))

Model 1

The model takes a while to run. Run next line to load model output from previous run with structure below.

# mm.1 <- readRDS("06_mp_model.rds")
mm.1 <- readRDS("06_mp_model_v2.rds")
mm.1 = glmer(correct ~ delay * norm_age + 
               task_experience + cup_distance + board_size + trial +
               (1 + delay + trial | site/subject_site/block) + 
               (1 + task_experience + cup_distance + board_size + trial + delay | species)
             , data = model.data
             , family = binomial
             , control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 2e5))
             )

saveRDS(mm.1, "06_mp_model_v2.rds")

Some diagnostics

  • examining Cholesky decomposition
theta <- getME(mm.1, "theta")
diag.element <- getME(mm.1, "lower") == 0
any(theta[diag.element] < 1e-5)
[1] TRUE

Model summary

Confirm model structure

# mm.1@call
formula(mm.1)
correct ~ delay * norm_age + task_experience + cup_distance + 
    board_size + trial + (1 + delay + trial | site/subject_site/block) + 
    (1 + task_experience + cup_distance + board_size + trial + 
        delay | species)
glance(mm.1) %>% kable(digits = 2)
sigma logLik AIC BIC deviance df.residual
1 -3385.22 6906.44 7364.74 6364.34 6178

Random effects

fmt = function(num, digits) return(round(num, digits))
VarCorr(mm.1) %>% print(formatter = fmt, digits = 3) # comp = c("Variance", "Std.Dev.")
 Groups                    Name               Std.Dev. Corr                               
 block:(subject_site:site) (Intercept)        0                                           
                           delaylong          0         NaN                               
                           delaymedium        0         NaN  0.95                         
                           trial              0         NaN  0.76  0.91                   
 subject_site:site         (Intercept)        0.861                                       
                           delaylong          0.653    -0.91                              
                           delaymedium        0.533    -0.85  0.99                        
                           trial              0.093    -0.25  0.63  0.72                  
 site                      (Intercept)        0.849                                       
                           delaylong          0.54     -1.00                              
                           delaymedium        0.652    -0.99  1.00                        
                           trial              0.092     0.86 -0.81 -0.81                  
 species                   (Intercept)        0.532                                       
                           task_experienceyes 0.066     1.00                              
                           cup_distance       0.025    -1.00 -1.00                        
                           board_size         0.112    -1.00 -1.00  1.00                  
                           trial              0.005    -1.00 -1.00  1.00  1.00            
                           delaylong          0.449    -1.00 -1.00  1.00  1.00  1.00      
                           delaymedium        0.388    -1.00 -1.00  1.00  1.00  1.00  1.00

Fixed effects

CIs

mm.1.ci = confint(mm.1, method = 'Wald') %>% # bootstrap these later
  as.data.frame %>% 
  rownames_to_column %>% 
  filter(complete.cases(.)) %>% 
  rename(LL = `2.5 %`, UL = `97.5 %`) %>%
  mutate(OR_LL = exp(LL), OR_UL = exp(UL))
coef(summary(mm.1)) %>% 
  as.data.frame %>% 
  rownames_to_column() %>%
  mutate(OR = exp(Estimate)) %>%
  left_join(mm.1.ci, by = 'rowname') %>%
  select(rowname, OR, OR_LL, OR_UL, Estimate, LL, UL, everything()) %>%
  kable(digits = 3)
rowname OR OR_LL OR_UL Estimate LL UL Std. Error z value Pr(>|z|)
(Intercept) 5.243 2.440 11.266 1.657 0.892 2.422 0.390 4.245 0.000
delaylong 0.262 0.150 0.457 -1.340 -1.897 -0.783 0.284 -4.716 0.000
delaymedium 0.340 0.190 0.608 -1.080 -1.663 -0.497 0.297 -3.632 0.000
norm_age 1.021 0.824 1.265 0.021 -0.194 0.235 0.109 0.188 0.851
task_experienceyes 1.019 0.714 1.454 0.019 -0.337 0.375 0.181 0.105 0.917
cup_distance 1.997 1.550 2.573 0.692 0.438 0.945 0.129 5.352 0.000
board_size 1.278 0.983 1.662 0.245 -0.017 0.508 0.134 1.831 0.067
trial 1.029 0.922 1.148 0.029 -0.081 0.138 0.056 0.510 0.610
delaylong:norm_age 1.015 0.820 1.256 0.015 -0.198 0.228 0.109 0.136 0.892
delaymedium:norm_age 1.058 0.859 1.304 0.056 -0.152 0.265 0.106 0.530 0.596
corr = cov2cor(vcov(mm.1)) %>% as.matrix %>% round(2)
corr[upper.tri(corr, diag = T)] = ''
colnames(corr) = 1:10
rownames(corr) = str_c(1:10, ' ', rownames(corr))

corr %>% as.data.frame %>% select(-10) %>% rownames_to_column

Pairwise contrasts for delay

based on estimated marginal means

Note. This wasn’t in the preregistration but, of course, yields the same conclusion as specifying another model with, say, ‘medium’ as the reference level or running another model that excludes ‘short’. And it’s a more elegant approach in my opinion. Thoughts? –jw

emmeans(mm.1, pairwise ~ delay, type = 'response')$contrasts
 contrast       odds.ratio         SE  df z.ratio p.value
 short / long    3.8183950 1.08483113 Inf   4.716  <.0001
 short / medium  2.9435164 0.87514908 Inf   3.631  0.0008
 long / medium   0.7708779 0.07336213 Inf  -2.734  0.0172

Results are averaged over the levels of: task_experience 
P value adjustment: tukey method for comparing a family of 3 estimates 
Tests are performed on the log odds ratio scale 

Model 1 plots

Fixed effects

plot_model(mm.1, title = 'Fixed Effects', order.terms = c(7, 4, 3:1, 9:8, 5, 6), width = .3,
           show.values = T, value.size = 2.5, value.offset = .3) +
  geom_hline(yintercept = 1, lty = 2) +
  ylim(0, 3)

Random effects

ranef.plots = plot_model(mm.1, type = 're', sort.est = '(Intercept)')

Block/Subject/Site

In line with the model summary above, there’s essentially zero variability in the random effects estimates for this.

ranef.plots[[1]]

Subject/Site

ranef.plots[[2]]

Site

ranef.plots[[3]]

Species

ranef.plots[[4]]


Pruning the model

  • remove block from random effects as the estimates in the previous models were essentially 0
  • same for trial random slopes within species
correct ~ delay * norm_age + 
          task_experience + cup_distance + board_size + trial +
          (1 + delay + trial | site/subject_site ) +         
          (1 + task_experience + cup_distance + board_size + delay | species)

Check colinearity in the previous model

col.mm1 <- glm(correct ~ delay + norm_age + 
                 task_experience + cup_distance + board_size + trial
               , data = model.data
               , family = binomial)
vif(col.mm1)
                    GVIF Df GVIF^(1/(2*Df))
delay           1.008742  2        1.002178
norm_age        1.111308  1        1.054186
task_experience 1.048604  1        1.024013
cup_distance    2.068924  1        1.438375
board_size      1.945906  1        1.394957
trial           1.000394  1        1.000197

board_size and cup_distance show high colinearity

Remove board_size as it is highly correlated with cup_distance. Cup distance seems to be of more immediate relevance.

correct ~ delay * norm_age + 
          task_experience + cup_distance + trial +
          (1 + delay + trial | site/subject_site ) +         
          (1 + task_experience + cup_distance + delay | species)

Check levels of random effects

Check how many different levels there are within each random effect

source("diagnostic_fcns.r") 
Overview = fe.re.tab("correct ~ delay + task_experience + cup_distance + trial", "species", data = model.data)
Overview$summary
$`delay_within_species (factor)`

 3 
11 

$`task_experience_within_species (factor)`

1 2 
9 2 

$`cup_distance_within_species (covariate)`

1 2 4 
7 3 1 

$`trial_within_species (covariate)`

36 
11 

This suggests that, within species, random slopes for task_experience does not make much sense as most species have only 1 level. Same is true for cup_distance. Indeed, the model summary and random effects plot for species confirm that there is little variability in these estimates (they’re close to zero). Therefore they are removed.

correct ~ delay * norm_age + 
          task_experience + cup_distance + trial +
          (1 + delay + trial | site/subject_site ) +         
          (1 + delay | species)

Model 2

The model takes a while to run. Run next line to load model output from previous run with structure below.

mm.2 <- readRDS("06_2_mp_model2_v2.rds")
# mm.2.ci<- readRDS("06_2_mp_model2_ci_v2.rds")
mm.2 <- glmer(correct ~ delay * norm_age +
              task_experience + cup_distance +  trial +
              (1 + trial + delay | site / subject_site ) +         
              (1 + delay | species)
              , data = model.data
              , family = binomial
              , control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 2e5))
              )

saveRDS(mm.2, "06_2_mp_model2_v2.rds")

Model 3

I would argue to also remove trial from the random slopes for subject/species as it’s near zero both in mm.1 and even more so in mm.2. I’ve done so below, in mm.3. Thoughts? –jw

VarCorr(mm.2) %>% print(comp = c("Variance", "Std.Dev."), formatter = fmt, digits = 3)
 Groups            Name        Variance Std.Dev. Corr             
 subject_site:site (Intercept) 0.756    0.869                     
                   trial       0.008    0.092    -0.25            
                   delaylong   0.43     0.656    -0.91  0.63      
                   delaymedium 0.288    0.536    -0.84  0.74  0.99
 site              (Intercept) 0.936    0.968                     
                   trial       0.008    0.09      0.86            
                   delaylong   0.4      0.632    -1.00 -0.81      
                   delaymedium 0.499    0.707    -0.99 -0.79  1.00
 species           (Intercept) 0.353    0.594                     
                   delaylong   0.19     0.436    -1.00            
                   delaymedium 0.132    0.363    -1.00  1.00      
plot_model(mm.2, type = 're', sort.est = '(Intercept)')[[1]]

plot_model(mm.2, type = 're', sort.est = '(Intercept)')[[2]]

The model takes a while to run. Run next line to load model output from previous run with structure below.

mm.3 <- readRDS("06_3_mp_model3_v2.rds")
mm.3 <- glmer(correct ~ delay * norm_age +
              task_experience + cup_distance +  trial +
              (1 + delay | site / subject_site ) +         
              (1 + delay | species)
              , data = model.data
              , family = binomial
              , control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 2e5))
              )

saveRDS(mm.4, "06_3_mp_model3_v2.rds")

Model summary

Confirm model structure

formula(mm.3)
correct ~ delay * norm_age + task_experience + cup_distance + 
    trial + (1 + delay | site/subject_site) + (1 + delay | species)
glance(mm.3) %>% kable(digits = 2)
sigma logLik AIC BIC deviance df.residual
1 -3391.12 6836.24 7018.21 6386.6 6219

Random effects

VarCorr(mm.3) %>% print(comp = c("Variance", "Std.Dev."), formatter = fmt, digits = 3)
 Groups            Name        Variance Std.Dev. Corr       
 subject_site:site (Intercept) 0.752    0.867               
                   delaylong   0.427    0.654    -0.90      
                   delaymedium 0.27     0.519    -0.85  0.99
 site              (Intercept) 1.018    1.009               
                   delaylong   0.472    0.687    -1.00      
                   delaymedium 0.547    0.74     -1.00  1.00
 species           (Intercept) 0.345    0.587               
                   delaylong   0.19     0.436    -1.00      
                   delaymedium 0.134    0.367    -1.00  1.00

Fixed effects

CIs

# this is not currently run
source("boot_glmm.r")

mm.3.ci = boot.glmm.pred(model.res=mm.3, excl.warnings=F, nboots=1000, para=F, resol=100, level=0.95, use=NULL, circ.var.name=NULL, circ.var=NULL, use.u=F,n.cores=c("all-1", "all"), save.path=NULL)

saveRDS(mm.2.ci, "06_2_mp_model2_ci_v2.rds")
mm.3.ci = confint(mm.3, method = 'Wald') %>% # bootstrap these later
  as.data.frame %>% 
  rownames_to_column %>% 
  filter(complete.cases(.)) %>% 
  rename(LL = `2.5 %`, UL = `97.5 %`) %>%
  mutate(OR_LL = exp(LL), OR_UL = exp(UL))
coef(summary(mm.3)) %>% 
  as.data.frame %>% 
  rownames_to_column() %>%
  mutate(OR = exp(Estimate)) %>%
  left_join(mm.3.ci, by = 'rowname') %>%
  select(rowname, OR, OR_LL, OR_UL, Estimate, LL, UL, everything()) %>%
  kable(digits = 3)
rowname OR OR_LL OR_UL Estimate LL UL Std. Error z value Pr(>|z|)
(Intercept) 6.693 2.920 15.343 1.901 1.072 2.731 0.423 4.492 0.000
delaylong 0.229 0.127 0.412 -1.476 -2.066 -0.886 0.301 -4.905 0.000
delaymedium 0.298 0.166 0.537 -1.210 -1.797 -0.623 0.299 -4.039 0.000
norm_age 1.023 0.824 1.269 0.022 -0.194 0.238 0.110 0.203 0.839
task_experienceyes 0.943 0.701 1.269 -0.058 -0.355 0.238 0.151 -0.385 0.700
cup_distance 2.112 1.699 2.624 0.747 0.530 0.965 0.111 6.743 0.000
trial 1.027 0.968 1.090 0.027 -0.033 0.086 0.030 0.874 0.382
delaylong:norm_age 1.009 0.817 1.246 0.009 -0.202 0.220 0.108 0.083 0.934
delaymedium:norm_age 1.050 0.857 1.288 0.049 -0.155 0.253 0.104 0.474 0.636

Pairwise contrasts for delay

based on estimated marginal means

emmeans(mm.3, pairwise ~ delay, type = 'response')$contrasts
 contrast       odds.ratio         SE  df z.ratio p.value
 short / long    4.3746081 1.31625922 Inf   4.905  <.0001
 short / medium  3.3515380 1.00366654 Inf   4.039  0.0002
 long / medium   0.7661345 0.06572278 Inf  -3.105  0.0054

Results are averaged over the levels of: task_experience 
P value adjustment: tukey method for comparing a family of 3 estimates 
Tests are performed on the log odds ratio scale 

Model 3 plots

Fixed effects

plot_model(mm.3, title = "Fixed Effects", order.terms = c(6, 4, 3:1, 8:7, 5), width = .3,
           show.values = T, value.size = 2.5, value.offset = .3) +
  geom_hline(yintercept = 1, lty = 2) +
  ylim(.05, 3)

ggsave('../graphs/07_forestplot.png', width = 4, height = 2.5, scale = 2)

Random effects

ranef.plots2 = plot_model(mm.3, type = 're', sort.est = '(Intercept)')

Subject/Site

ranef.plots2[[1]]

Site

ranef.plots2[[2]]

Species

ranef.plots2[[3]]

Model 4

  • further remove subject/site random effects

Note. I’m not sure why this is here. It runs fast and is simpler but it’s also worse than the less reduced model/s (mm.2/3) and also worse than the full(ish) model (mm.1); see below. –jw

mm.4 <- glmer(correct ~ delay * norm_age +
                task_experience + cup_distance + trial +
                (1 + delay | species)
              , data = model.data
              , family = binomial
              , control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 2e5))
        )

Model comparison

We’re looking for the lowest AIC(c) as the model with the ‘best fit’ with a reasonable number of parameters. (Too many are penalized by AIC as one way to address overfitting.)

Indeed, the reduced model seems to do a better job of striking that balance between fitting the data with fewer parameters.

bbmle::AICctab(mm.1, mm.2, mm.3, mm.4)
     dAICc df
mm.3   0.0 27
mm.2  11.1 35
mm.1  71.5 68
mm.4 143.2 15
anova(mm.1, mm.2, mm.3, mm.4)
Data: model.data
Models:
mm.4: correct ~ delay * norm_age + task_experience + cup_distance + 
mm.4:     trial + (1 + delay | species)
mm.3: correct ~ delay * norm_age + task_experience + cup_distance + 
mm.3:     trial + (1 + delay | site/subject_site) + (1 + delay | species)
mm.2: correct ~ delay * norm_age + task_experience + cup_distance + 
mm.2:     trial + (1 + trial + delay | site/subject_site) + (1 + delay | 
mm.2:     species)
mm.1: correct ~ delay * norm_age + task_experience + cup_distance + 
mm.1:     board_size + trial + (1 + delay + trial | site/subject_site/block) + 
mm.1:     (1 + task_experience + cup_distance + board_size + trial + 
mm.1:         delay | species)
     Df    AIC    BIC  logLik deviance    Chisq Chi Df Pr(>Chisq)    
mm.4 15 6979.6 7080.7 -3474.8   6949.6                               
mm.3 27 6836.2 7018.2 -3391.1   6782.2 167.3967     12     <2e-16 ***
mm.2 35 6847.1 7083.0 -3388.6   6777.1   5.0907      8     0.7478    
mm.1 68 6906.4 7364.7 -3385.2   6770.4   6.7083     33     1.0000    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Difference in regression coefficients

Difference

coef1 = coef(summary(mm.1))[c(2,3,6), 1]
coef2 = coef(summary(mm.3))[c(2,3,6), 1]
coef2 - coef1
   delaylong  delaymedium cup_distance 
  -0.1359696   -0.1297933    0.0556988 

Difference in odds ratios

exp(coef2) - exp(coef1)
   delaylong  delaymedium cup_distance 
 -0.03329287  -0.04134608   0.11440274 
LS0tCnRpdGxlOiAiTWFueVByaW1hdGVzIHBpbG90IG1peGVkIG1vZGVsaW5nIgphdXRob3I6ICJEcmV3IEFsdHNjaHVsIgpkYXRlOiAiMTUgT2N0IDIwMTgiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY3NzOiBzdHlsZS5jc3MKICAgIHRoZW1lOiBwYXBlcgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCi0tLQoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGxtZTQpCmxpYnJhcnkoZW1tZWFucykKbGlicmFyeShjYXIpICMgZm9yIHZpZgpsaWJyYXJ5KHNqUGxvdCkKbGlicmFyeShicm9vbSkKbGlicmFyeShrbml0cikKCnRoZW1lX3NldChnZ3RoZW1lczo6dGhlbWVfZmV3KCkpCmBgYAoKIyBTdW1tYXJ5CgpNaXhlZCBtb2RlbGluZyB3aXRoIGFsbCByZWxldmFudCB2YXJpYWJsZXMgcHJlZGljdGluZyBhY2N1cmFjeQoKRnJvbSB0aGUgcHJlcmVnaXN0cmF0aW9uLCB0aGUgbWl4ZWQgbW9kZWwgd2FzIHNwZWNpZmllZCB0aHVzbHk6CgpgYGAKY29ycmVjdCB+IGRlbGF5ICogYWdlICsgCiAgICAgICAgICB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyBib2FyZF9zaXplICsgdHJpYWwgKwogICAgICAgICAgKDEgKyBkZWxheSArIHRyaWFsIHwgc2l0ZS9zdWJqZWN0L2Jsb2NrL2hpZGluZ19sb2NhdGlvbiApICsgCiAgICAgICAgICAoMSArIHRhc2tfZXhwZXJpZW5jZSArIGN1cF9kaXN0YW5jZSArIGJvYXJkX3NpemUgKyB0cmlhbCArIGRlbGF5IHwgc3BlY2llcykKYGBgCgpJbiB0aGUgZGF0YWZyYW1lLCAKYHN1YmplY3Rfc2l0ZSA9IHN1YmplY3RgLAphbmQgYG5vcm1fYWdlYCBzaG91bGQgYmUgdXNlZCBmb3IgYGFnZWAuCgpNb2RlbCBhcyBwcmUtcmVnaXN0ZXJlZCBoYXMgdG9vIG1hbnkgcmFuZG9tIGVmZmVjdHMKCmBgYApFcnJvcjogbnVtYmVyIG9mIG9ic2VydmF0aW9ucyAoPTYyNDYpIDwgbnVtYmVyIG9mIHJhbmRvbSBlZmZlY3RzICg9MTA2MDgpIGZvciB0ZXJtICgxICsgZGVsYXkgKyB0cmlhbCB8IGhpZGluZ19sb2NhdGlvbjooYmxvY2s6KHN1YmplY3Rfc2l0ZTpzaXRlKSkpOyB0aGUgcmFuZG9tLWVmZmVjdHMgcGFyYW1ldGVycyBhcmUgcHJvYmFibHkgdW5pZGVudGlmaWFibGUKYGBgCgpQcnVuaW5nIHJhbmRvbSBlZmZlY3RzIGluIHRoZSBmb2xsb3dpbmcgb3JkZXIgKGZyb20gcHJlcmVnaXN0cmF0aW9uKTogCgo+IC0gUmVtb3ZlIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHJhbmRvbSBlZmZlY3RzCj4gLSBSZW1vdmUgcmFuZG9tIHNsb3BlcyAoaW4gdGhlIGZvbGxvd2luZyBvcmRlcikKPiAgICAgLSBgc3BlY2llc2AKPiAgICAgLSBgaGlkaW5nX2xvY2F0aW9uYAo+ICAgICAtIGBibG9ja2AKPiAgICAgLSBgc3ViamVjdGAKCk1vZGVsIG9ubHkgY29udmVyZ2VzIG9uY2Ugd2UgdGFrZSBvdXQgYGhpZGluZ19sb2NhdGlvbmAuIEFmdGVyIGRvaW5nIHNvLCB0aGUgb3RoZXIgcmFuZG9tIGVmZmVjdHMgKGNvcnJlbGF0aW9uLCBzaXRlLCBzcGVjaWVzKSBjYW4gYmUgcHV0IGJhY2sgaW4uCgpUaGUgbW9kZWwgYmVsb3cgY29udmVyZ2VzLiBNb2RlbCBvdXRwdXQgaXMgc2F2ZWQgaW4gYDA2X21wX21vZGVsX3YyLnJkc2AKCmBgYApjb3JyZWN0IH4gZGVsYXkgKiBub3JtX2FnZSArIAogICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsICsgCiAgICAgICAgICAoMSArIGRlbGF5ICsgdHJpYWwgfCBzaXRlL3N1YmplY3Rfc2l0ZS9ibG9jaykgKyAKICAgICAgICAgICgxICsgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsICsgZGVsYXkgfCBzcGVjaWVzKQpgYGAKCiMjIFJlZHVjZWQgbW9kZWwKCkFmdGVyIHBydW5pbmcgcmFuZG9tIGVmZmVjdHMgd2l0aCBsaXR0bGUgdmFyaWFiaWxpdHkgYW5kIHJlbW92aW5nIGBib2FyZF9zaXplYCwgd2hpY2ggY292YXJpZWQgd2l0aCBgY3VwX2Rpc3RhbmNlYCwgdGhlIHJlZHVjZWQgbW9kZWwgaGFzIHRoZSBmb2xsb3dpbmcgc3RydWN0dXJlLiBJdCBpcyBzYXZlZCBpbiBgMDZfbXBfM19tb2RlbDNfdjIucmRzYAoKYGBgCmNvcnJlY3QgfiBkZWxheSAqIG5vcm1fYWdlICsgCiAgICAgICAgICB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyB0cmlhbCArIAogICAgICAgICAgKDEgKyBkZWxheSB8IHNpdGUvc3ViamVjdF9zaXRlKSArIAogICAgICAgICAgKDEgKyBkZWxheSB8IHNwZWNpZXMpCmBgYAoKIVtdKC4uL2dyYXBocy8wN19mb3Jlc3RwbG90LnBuZykKCioqKgoKIyBEYXRhIHByZXAKCkRhdGEgaW1wb3J0CgpgYGB7ciBsb2FkaW5nIGRhdGF9Cm1wX2RhdGEgPC0gcmVhZC5jc3YoIi4uL2RhdGEvbWVyZ2VkX2RhdGEvMDFfbWFueXByaW1hdGVzX3BpbG90X21lcmdlZF9kYXRhX3YyLmNzdiIpCmBgYAoKUHJlcGFyZSBjb2RlIGZvciBwcmUtcmVnaXN0ZXJlZCBtaXhlZCBtb2RlbGluZwoKLSBjZW50ZXIgYGN1cF9kaXN0YW5jZWAsIGBib2FyZF9zaXplYCBhbmQgYHRyaWFsYAotIGZpbHRlciBvdXQgc3BpZGVyIG1vbmtleS4gT25seSBvbmUgZGF0YSBwb2ludCBzbyBmYXIsIHRoZXJlZm9yZSB0aGlzIGlzIG5vdCB3b3J0aCBpbmNsdWRpbmcgdG8gZXhwbG9kZSB0aGUgbnVtYmVyIG9mIHJhbmRvbSBlZmZlY3RzCgpgYGB7cn0KbW9kZWwuZGF0YSA8LSBtcF9kYXRhICU+JQogIGZpbHRlcihzcGVjaWVzICE9ICJibGFja19mYWNlZF9zcGlkZXJfbW9ua2V5IikgJT4lCiAgbXV0YXRlX2F0KHZhcnMoY3VwX2Rpc3RhbmNlLCBib2FyZF9zaXplLCB0cmlhbCksIGZ1bnMoc2NhbGUoLilbLDFdKSkgJT4lCiAgbXV0YXRlKGhpZGluZ19sb2NhdGlvbiA9IGZhY3RvcihoaWRpbmdfbG9jYXRpb24pLAogICAgICAgICBkZWxheSA9IGZjdF9yZWxldmVsKGRlbGF5LCAic2hvcnQiKSkKYGBgCgojIE1vZGVsIDEKClRoZSBtb2RlbCB0YWtlcyBhIHdoaWxlIHRvIHJ1bi4gUnVuIG5leHQgbGluZSB0byBsb2FkIG1vZGVsIG91dHB1dCBmcm9tIHByZXZpb3VzIHJ1biB3aXRoIHN0cnVjdHVyZSBiZWxvdy4KCmBgYHtyfQojIG1tLjEgPC0gcmVhZFJEUygiMDZfbXBfbW9kZWwucmRzIikKbW0uMSA8LSByZWFkUkRTKCIwNl9tcF9tb2RlbF92Mi5yZHMiKQpgYGAKCmBgYHtyLCBldmFsPUZBTFNFfQptbS4xID0gZ2xtZXIoY29ycmVjdCB+IGRlbGF5ICogbm9ybV9hZ2UgKyAKICAgICAgICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsICsKICAgICAgICAgICAgICAgKDEgKyBkZWxheSArIHRyaWFsIHwgc2l0ZS9zdWJqZWN0X3NpdGUvYmxvY2spICsgCiAgICAgICAgICAgICAgICgxICsgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsICsgZGVsYXkgfCBzcGVjaWVzKQogICAgICAgICAgICAgLCBkYXRhID0gbW9kZWwuZGF0YQogICAgICAgICAgICAgLCBmYW1pbHkgPSBiaW5vbWlhbAogICAgICAgICAgICAgLCBjb250cm9sID0gZ2xtZXJDb250cm9sKG9wdGltaXplciA9ICJib2J5cWEiLCBvcHRDdHJsID0gbGlzdChtYXhmdW4gPSAyZTUpKQogICAgICAgICAgICAgKQoKc2F2ZVJEUyhtbS4xLCAiMDZfbXBfbW9kZWxfdjIucmRzIikKYGBgCgpTb21lIGRpYWdub3N0aWNzCgotIGV4YW1pbmluZyBDaG9sZXNreSBkZWNvbXBvc2l0aW9uCgpgYGB7cn0KdGhldGEgPC0gZ2V0TUUobW0uMSwgInRoZXRhIikKZGlhZy5lbGVtZW50IDwtIGdldE1FKG1tLjEsICJsb3dlciIpID09IDAKYW55KHRoZXRhW2RpYWcuZWxlbWVudF0gPCAxZS01KQpgYGAKCiMjIE1vZGVsIHN1bW1hcnkKCkNvbmZpcm0gbW9kZWwgc3RydWN0dXJlCgpgYGB7cn0KIyBtbS4xQGNhbGwKZm9ybXVsYShtbS4xKQpgYGAKCmBgYHtyLCByZXN1bHRzPSdhc2lzJ30KZ2xhbmNlKG1tLjEpICU+JSBrYWJsZShkaWdpdHMgPSAyKQpgYGAKCiMjIFJhbmRvbSBlZmZlY3RzCgpgYGB7cn0KZm10ID0gZnVuY3Rpb24obnVtLCBkaWdpdHMpIHJldHVybihyb3VuZChudW0sIGRpZ2l0cykpClZhckNvcnIobW0uMSkgJT4lIHByaW50KGZvcm1hdHRlciA9IGZtdCwgZGlnaXRzID0gMykgIyBjb21wID0gYygiVmFyaWFuY2UiLCAiU3RkLkRldi4iKQpgYGAKCiMjIEZpeGVkIGVmZmVjdHMKCkNJcwoKYGBge3J9Cm1tLjEuY2kgPSBjb25maW50KG1tLjEsIG1ldGhvZCA9ICdXYWxkJykgJT4lICMgYm9vdHN0cmFwIHRoZXNlIGxhdGVyCiAgYXMuZGF0YS5mcmFtZSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uICU+JSAKICBmaWx0ZXIoY29tcGxldGUuY2FzZXMoLikpICU+JSAKICByZW5hbWUoTEwgPSBgMi41ICVgLCBVTCA9IGA5Ny41ICVgKSAlPiUKICBtdXRhdGUoT1JfTEwgPSBleHAoTEwpLCBPUl9VTCA9IGV4cChVTCkpCmBgYAoKYGBge3IsIHJlc3VsdHM9J2FzaXMnfQpjb2VmKHN1bW1hcnkobW0uMSkpICU+JSAKICBhcy5kYXRhLmZyYW1lICU+JSAKICByb3duYW1lc190b19jb2x1bW4oKSAlPiUKICBtdXRhdGUoT1IgPSBleHAoRXN0aW1hdGUpKSAlPiUKICBsZWZ0X2pvaW4obW0uMS5jaSwgYnkgPSAncm93bmFtZScpICU+JQogIHNlbGVjdChyb3duYW1lLCBPUiwgT1JfTEwsIE9SX1VMLCBFc3RpbWF0ZSwgTEwsIFVMLCBldmVyeXRoaW5nKCkpICU+JQogIGthYmxlKGRpZ2l0cyA9IDMpCmBgYAoKPCEtLSAjIyBDb3JyZWxhdGlvbiBvZiBGaXhlZCBFZmZlY3RzIC0tPgoKYGBge3IsIGV2YWw9RkFMU0UsIHJlc3VsdHM9J2FzaXMnfQpjb3JyID0gY292MmNvcih2Y292KG1tLjEpKSAlPiUgYXMubWF0cml4ICU+JSByb3VuZCgyKQpjb3JyW3VwcGVyLnRyaShjb3JyLCBkaWFnID0gVCldID0gJycKY29sbmFtZXMoY29ycikgPSAxOjEwCnJvd25hbWVzKGNvcnIpID0gc3RyX2MoMToxMCwgJyAnLCByb3duYW1lcyhjb3JyKSkKCmNvcnIgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIHNlbGVjdCgtMTApICU+JSByb3duYW1lc190b19jb2x1bW4KYGBgCgojIyBQYWlyd2lzZSBjb250cmFzdHMgZm9yIGRlbGF5CgpiYXNlZCBvbiBlc3RpbWF0ZWQgbWFyZ2luYWwgbWVhbnMKCipOb3RlLiBUaGlzIHdhc24ndCBpbiB0aGUgcHJlcmVnaXN0cmF0aW9uIGJ1dCwgb2YgY291cnNlLCB5aWVsZHMgdGhlIHNhbWUgY29uY2x1c2lvbiBhcyBzcGVjaWZ5aW5nIGFub3RoZXIgbW9kZWwgd2l0aCwgc2F5LCAnbWVkaXVtJyBhcyB0aGUgcmVmZXJlbmNlIGxldmVsIG9yIHJ1bm5pbmcgYW5vdGhlciBtb2RlbCB0aGF0IGV4Y2x1ZGVzICdzaG9ydCcuIEFuZCBpdCdzIGEgbW9yZSBlbGVnYW50IGFwcHJvYWNoIGluIG15IG9waW5pb24uIFRob3VnaHRzPyAtLWp3KgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmVtbWVhbnMobW0uMSwgcGFpcndpc2UgfiBkZWxheSwgdHlwZSA9ICdyZXNwb25zZScpJGNvbnRyYXN0cwpgYGAKCiMgTW9kZWwgMSBwbG90cwoKIyMgRml4ZWQgZWZmZWN0cwoKYGBge3IsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTIuNSwgbWVzc2FnZT1GQUxTRX0KcGxvdF9tb2RlbChtbS4xLCB0aXRsZSA9ICdGaXhlZCBFZmZlY3RzJywgb3JkZXIudGVybXMgPSBjKDcsIDQsIDM6MSwgOTo4LCA1LCA2KSwgd2lkdGggPSAuMywKICAgICAgICAgICBzaG93LnZhbHVlcyA9IFQsIHZhbHVlLnNpemUgPSAyLjUsIHZhbHVlLm9mZnNldCA9IC4zKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgbHR5ID0gMikgKwogIHlsaW0oMCwgMykKYGBgCgojIyBSYW5kb20gZWZmZWN0cwoKYGBge3J9CnJhbmVmLnBsb3RzID0gcGxvdF9tb2RlbChtbS4xLCB0eXBlID0gJ3JlJywgc29ydC5lc3QgPSAnKEludGVyY2VwdCknKQpgYGAKCiMjIyBCbG9jay9TdWJqZWN0L1NpdGUKCkluIGxpbmUgd2l0aCB0aGUgbW9kZWwgc3VtbWFyeSBhYm92ZSwgdGhlcmUncyBlc3NlbnRpYWxseSB6ZXJvIHZhcmlhYmlsaXR5IGluIHRoZSByYW5kb20gZWZmZWN0cyBlc3RpbWF0ZXMgZm9yIHRoaXMuCgpgYGB7ciBmaWcuaGVpZ2h0PTIwLCBmaWcud2lkdGg9MTB9CnJhbmVmLnBsb3RzW1sxXV0KYGBgCgojIyMgU3ViamVjdC9TaXRlCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTh9CnJhbmVmLnBsb3RzW1syXV0KYGBgCgojIyMgU2l0ZQoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0zfQpyYW5lZi5wbG90c1tbM11dCmBgYAoKIyMjIFNwZWNpZXMKCmBgYHtyIHJhbmVmIHNwZWNpZXMsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTJ9CnJhbmVmLnBsb3RzW1s0XV0KYGBgCgoqKioKCiMgUHJ1bmluZyB0aGUgbW9kZWwKCi0gcmVtb3ZlIGBibG9ja2AgZnJvbSByYW5kb20gZWZmZWN0cyBhcyB0aGUgZXN0aW1hdGVzIGluIHRoZSBwcmV2aW91cyBtb2RlbHMgd2VyZSBlc3NlbnRpYWxseSAwCi0gc2FtZSBmb3IgYHRyaWFsYCByYW5kb20gc2xvcGVzIHdpdGhpbiBgc3BlY2llc2AKCmBgYApjb3JyZWN0IH4gZGVsYXkgKiBub3JtX2FnZSArIAogICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsICsKICAgICAgICAgICgxICsgZGVsYXkgKyB0cmlhbCB8IHNpdGUvc3ViamVjdF9zaXRlICkgKyAgICAgICAgIAogICAgICAgICAgKDEgKyB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyBib2FyZF9zaXplICsgZGVsYXkgfCBzcGVjaWVzKQpgYGAKCiMjIENoZWNrIGNvbGluZWFyaXR5IGluIHRoZSBwcmV2aW91cyBtb2RlbAoKYGBge3J9CmNvbC5tbTEgPC0gZ2xtKGNvcnJlY3QgfiBkZWxheSArIG5vcm1fYWdlICsgCiAgICAgICAgICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsCiAgICAgICAgICAgICAgICwgZGF0YSA9IG1vZGVsLmRhdGEKICAgICAgICAgICAgICAgLCBmYW1pbHkgPSBiaW5vbWlhbCkKCnZpZihjb2wubW0xKQpgYGAKCmBib2FyZF9zaXplYCBhbmQgYGN1cF9kaXN0YW5jZWAgc2hvdyBoaWdoIGNvbGluZWFyaXR5CgpSZW1vdmUgYGJvYXJkX3NpemVgIGFzIGl0IGlzIGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggYGN1cF9kaXN0YW5jZWAuIEN1cCBkaXN0YW5jZSBzZWVtcyB0byBiZSBvZiBtb3JlIGltbWVkaWF0ZSByZWxldmFuY2UuCgpgYGAKY29ycmVjdCB+IGRlbGF5ICogbm9ybV9hZ2UgKyAKICAgICAgICAgIHRhc2tfZXhwZXJpZW5jZSArIGN1cF9kaXN0YW5jZSArIHRyaWFsICsKICAgICAgICAgICgxICsgZGVsYXkgKyB0cmlhbCB8IHNpdGUvc3ViamVjdF9zaXRlICkgKyAgICAgICAgIAogICAgICAgICAgKDEgKyB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyBkZWxheSB8IHNwZWNpZXMpCmBgYAoKIyMgQ2hlY2sgbGV2ZWxzIG9mIHJhbmRvbSBlZmZlY3RzCgpDaGVjayBob3cgbWFueSBkaWZmZXJlbnQgbGV2ZWxzIHRoZXJlIGFyZSB3aXRoaW4gZWFjaCByYW5kb20gZWZmZWN0CgpgYGB7cn0Kc291cmNlKCJkaWFnbm9zdGljX2ZjbnMuciIpIAoKT3ZlcnZpZXcgPSBmZS5yZS50YWIoImNvcnJlY3QgfiBkZWxheSArIHRhc2tfZXhwZXJpZW5jZSArIGN1cF9kaXN0YW5jZSArIHRyaWFsIiwgInNwZWNpZXMiLCBkYXRhID0gbW9kZWwuZGF0YSkKCk92ZXJ2aWV3JHN1bW1hcnkKYGBgCgpUaGlzIHN1Z2dlc3RzIHRoYXQsIHdpdGhpbiBzcGVjaWVzLCByYW5kb20gc2xvcGVzIGZvciBgdGFza19leHBlcmllbmNlYCBkb2VzIG5vdCBtYWtlIG11Y2ggc2Vuc2UgYXMgbW9zdCBzcGVjaWVzIGhhdmUgb25seSAxIGxldmVsLiBTYW1lIGlzIHRydWUgZm9yIGBjdXBfZGlzdGFuY2VgLiBJbmRlZWQsIHRoZSBtb2RlbCBzdW1tYXJ5IGFuZCByYW5kb20gZWZmZWN0cyBwbG90IGZvciBgc3BlY2llc2AgY29uZmlybSB0aGF0IHRoZXJlIGlzIGxpdHRsZSB2YXJpYWJpbGl0eSBpbiB0aGVzZSBlc3RpbWF0ZXMgKHRoZXkncmUgY2xvc2UgdG8gemVybykuIFRoZXJlZm9yZSB0aGV5IGFyZSByZW1vdmVkLgoKYGBgCmNvcnJlY3QgfiBkZWxheSAqIG5vcm1fYWdlICsgCiAgICAgICAgICB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyB0cmlhbCArCiAgICAgICAgICAoMSArIGRlbGF5ICsgdHJpYWwgfCBzaXRlL3N1YmplY3Rfc2l0ZSApICsgICAgICAgICAKICAgICAgICAgICgxICsgZGVsYXkgfCBzcGVjaWVzKQpgYGAKCiMgTW9kZWwgMgoKVGhlIG1vZGVsIHRha2VzIGEgd2hpbGUgdG8gcnVuLiBSdW4gbmV4dCBsaW5lIHRvIGxvYWQgbW9kZWwgb3V0cHV0IGZyb20gcHJldmlvdXMgcnVuIHdpdGggc3RydWN0dXJlIGJlbG93LgoKYGBge3J9Cm1tLjIgPC0gcmVhZFJEUygiMDZfMl9tcF9tb2RlbDJfdjIucmRzIikKIyBtbS4yLmNpPC0gcmVhZFJEUygiMDZfMl9tcF9tb2RlbDJfY2lfdjIucmRzIikKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KbW0uMiA8LSBnbG1lcihjb3JyZWN0IH4gZGVsYXkgKiBub3JtX2FnZSArCiAgICAgICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgIHRyaWFsICsKICAgICAgICAgICAgICAoMSArIHRyaWFsICsgZGVsYXkgfCBzaXRlIC8gc3ViamVjdF9zaXRlICkgKyAgICAgICAgIAogICAgICAgICAgICAgICgxICsgZGVsYXkgfCBzcGVjaWVzKQogICAgICAgICAgICAgICwgZGF0YSA9IG1vZGVsLmRhdGEKICAgICAgICAgICAgICAsIGZhbWlseSA9IGJpbm9taWFsCiAgICAgICAgICAgICAgLCBjb250cm9sID0gZ2xtZXJDb250cm9sKG9wdGltaXplciA9ICJib2J5cWEiLCBvcHRDdHJsID0gbGlzdChtYXhmdW4gPSAyZTUpKQogICAgICAgICAgICAgICkKCnNhdmVSRFMobW0uMiwgIjA2XzJfbXBfbW9kZWwyX3YyLnJkcyIpCmBgYAoKIyBNb2RlbCAzCgoqSSB3b3VsZCBhcmd1ZSB0byBhbHNvIHJlbW92ZSBgdHJpYWxgIGZyb20gdGhlIHJhbmRvbSBzbG9wZXMgZm9yIHN1YmplY3Qvc3BlY2llcyBhcyBpdCdzIG5lYXIgemVybyBib3RoIGluIGBtbS4xYCBhbmQgZXZlbiBtb3JlIHNvIGluIGBtbS4yYC4gSSd2ZSBkb25lIHNvIGJlbG93LCBpbiBgbW0uM2AuIFRob3VnaHRzPyAtLWp3KgoKYGBge3J9ClZhckNvcnIobW0uMikgJT4lIHByaW50KGNvbXAgPSBjKCJWYXJpYW5jZSIsICJTdGQuRGV2LiIpLCBmb3JtYXR0ZXIgPSBmbXQsIGRpZ2l0cyA9IDMpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04fQpwbG90X21vZGVsKG1tLjIsIHR5cGUgPSAncmUnLCBzb3J0LmVzdCA9ICcoSW50ZXJjZXB0KScpW1sxXV0KYGBgCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTN9CnBsb3RfbW9kZWwobW0uMiwgdHlwZSA9ICdyZScsIHNvcnQuZXN0ID0gJyhJbnRlcmNlcHQpJylbWzJdXQpgYGAKClRoZSBtb2RlbCB0YWtlcyBhIHdoaWxlIHRvIHJ1bi4gUnVuIG5leHQgbGluZSB0byBsb2FkIG1vZGVsIG91dHB1dCBmcm9tIHByZXZpb3VzIHJ1biB3aXRoIHN0cnVjdHVyZSBiZWxvdy4KCmBgYHtyfQptbS4zIDwtIHJlYWRSRFMoIjA2XzNfbXBfbW9kZWwzX3YyLnJkcyIpCmBgYAoKYGBge3IsIGV2YWw9RkFMU0V9Cm1tLjMgPC0gZ2xtZXIoY29ycmVjdCB+IGRlbGF5ICogbm9ybV9hZ2UgKwogICAgICAgICAgICAgIHRhc2tfZXhwZXJpZW5jZSArIGN1cF9kaXN0YW5jZSArICB0cmlhbCArCiAgICAgICAgICAgICAgKDEgKyBkZWxheSB8IHNpdGUgLyBzdWJqZWN0X3NpdGUgKSArICAgICAgICAgCiAgICAgICAgICAgICAgKDEgKyBkZWxheSB8IHNwZWNpZXMpCiAgICAgICAgICAgICAgLCBkYXRhID0gbW9kZWwuZGF0YQogICAgICAgICAgICAgICwgZmFtaWx5ID0gYmlub21pYWwKICAgICAgICAgICAgICAsIGNvbnRyb2wgPSBnbG1lckNvbnRyb2wob3B0aW1pemVyID0gImJvYnlxYSIsIG9wdEN0cmwgPSBsaXN0KG1heGZ1biA9IDJlNSkpCiAgICAgICAgICAgICAgKQoKc2F2ZVJEUyhtbS40LCAiMDZfM19tcF9tb2RlbDNfdjIucmRzIikKYGBgCgojIyBNb2RlbCBzdW1tYXJ5CgpDb25maXJtIG1vZGVsIHN0cnVjdHVyZQoKYGBge3J9CmZvcm11bGEobW0uMykKYGBgCgpgYGB7ciwgcmVzdWx0cz0nYXNpcyd9CmdsYW5jZShtbS4zKSAlPiUga2FibGUoZGlnaXRzID0gMikKYGBgCgojIyBSYW5kb20gZWZmZWN0cwoKYGBge3J9ClZhckNvcnIobW0uMykgJT4lIHByaW50KGNvbXAgPSBjKCJWYXJpYW5jZSIsICJTdGQuRGV2LiIpLCBmb3JtYXR0ZXIgPSBmbXQsIGRpZ2l0cyA9IDMpCmBgYAoKIyMgRml4ZWQgZWZmZWN0cwoKQ0lzCgo8IS0tIEJvb3RzdHJhcCBmdW5jdGlvbiBieSBSb2dlciBNdW5kcnkgQCBNUEkgRVZBIC0tPgoKYGBge3IsIGV2YWw9RkFMU0V9CiMgdGhpcyBpcyBub3QgY3VycmVudGx5IHJ1bgpzb3VyY2UoImJvb3RfZ2xtbS5yIikKCm1tLjMuY2kgPSBib290LmdsbW0ucHJlZChtb2RlbC5yZXM9bW0uMywgZXhjbC53YXJuaW5ncz1GLCBuYm9vdHM9MTAwMCwgcGFyYT1GLCByZXNvbD0xMDAsIGxldmVsPTAuOTUsIHVzZT1OVUxMLCBjaXJjLnZhci5uYW1lPU5VTEwsIGNpcmMudmFyPU5VTEwsIHVzZS51PUYsbi5jb3Jlcz1jKCJhbGwtMSIsICJhbGwiKSwgc2F2ZS5wYXRoPU5VTEwpCgpzYXZlUkRTKG1tLjIuY2ksICIwNl8yX21wX21vZGVsMl9jaV92Mi5yZHMiKQpgYGAKCmBgYHtyfQptbS4zLmNpID0gY29uZmludChtbS4zLCBtZXRob2QgPSAnV2FsZCcpICU+JSAjIGJvb3RzdHJhcCB0aGVzZSBsYXRlcgogIGFzLmRhdGEuZnJhbWUgJT4lIAogIHJvd25hbWVzX3RvX2NvbHVtbiAlPiUgCiAgZmlsdGVyKGNvbXBsZXRlLmNhc2VzKC4pKSAlPiUgCiAgcmVuYW1lKExMID0gYDIuNSAlYCwgVUwgPSBgOTcuNSAlYCkgJT4lCiAgbXV0YXRlKE9SX0xMID0gZXhwKExMKSwgT1JfVUwgPSBleHAoVUwpKQpgYGAKCmBgYHtyLCByZXN1bHRzPSdhc2lzJ30KY29lZihzdW1tYXJ5KG1tLjMpKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKCkgJT4lCiAgbXV0YXRlKE9SID0gZXhwKEVzdGltYXRlKSkgJT4lCiAgbGVmdF9qb2luKG1tLjMuY2ksIGJ5ID0gJ3Jvd25hbWUnKSAlPiUKICBzZWxlY3Qocm93bmFtZSwgT1IsIE9SX0xMLCBPUl9VTCwgRXN0aW1hdGUsIExMLCBVTCwgZXZlcnl0aGluZygpKSAlPiUKICBrYWJsZShkaWdpdHMgPSAzKQpgYGAKCiMjIFBhaXJ3aXNlIGNvbnRyYXN0cyBmb3IgZGVsYXkKCmJhc2VkIG9uIGVzdGltYXRlZCBtYXJnaW5hbCBtZWFucwoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmVtbWVhbnMobW0uMywgcGFpcndpc2UgfiBkZWxheSwgdHlwZSA9ICdyZXNwb25zZScpJGNvbnRyYXN0cwpgYGAKCiMgTW9kZWwgMyBwbG90cwoKIyMgRml4ZWQgZWZmZWN0cwoKYGBge3IsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTIuNSwgbWVzc2FnZT1GQUxTRX0KcGxvdF9tb2RlbChtbS4zLCB0aXRsZSA9ICJGaXhlZCBFZmZlY3RzIiwgb3JkZXIudGVybXMgPSBjKDYsIDQsIDM6MSwgODo3LCA1KSwgd2lkdGggPSAuMywKICAgICAgICAgICBzaG93LnZhbHVlcyA9IFQsIHZhbHVlLnNpemUgPSAyLjUsIHZhbHVlLm9mZnNldCA9IC4zKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgbHR5ID0gMikgKwogIHlsaW0oLjA1LCAzKQpgYGAKCmBgYHtyfQpnZ3NhdmUoJy4uL2dyYXBocy8wN19mb3Jlc3RwbG90LnBuZycsIHdpZHRoID0gNCwgaGVpZ2h0ID0gMi41LCBzY2FsZSA9IDIpCmBgYAoKIyMgUmFuZG9tIGVmZmVjdHMKCmBgYHtyfQpyYW5lZi5wbG90czIgPSBwbG90X21vZGVsKG1tLjMsIHR5cGUgPSAncmUnLCBzb3J0LmVzdCA9ICcoSW50ZXJjZXB0KScpCmBgYAoKIyMjIFN1YmplY3QvU2l0ZQoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04fQpyYW5lZi5wbG90czJbWzFdXQpgYGAKCiMjIyBTaXRlCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTN9CnJhbmVmLnBsb3RzMltbMl1dCmBgYAoKIyMjIFNwZWNpZXMKCmBgYHtyLCBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD0yfQpyYW5lZi5wbG90czJbWzNdXQpgYGAKCiMgTW9kZWwgNAoKLSBmdXJ0aGVyIHJlbW92ZSBzdWJqZWN0L3NpdGUgcmFuZG9tIGVmZmVjdHMKCipOb3RlLiBJJ20gbm90IHN1cmUgd2h5IHRoaXMgaXMgaGVyZS4gSXQgcnVucyBmYXN0IGFuZCBpcyBzaW1wbGVyIGJ1dCBpdCdzIGFsc28gd29yc2UgdGhhbiB0aGUgbGVzcyByZWR1Y2VkIG1vZGVsL3MgKG1tLjIvMykgYW5kIGFsc28gd29yc2UgdGhhbiB0aGUgZnVsbChpc2gpIG1vZGVsIChtbS4xKTsgc2VlIGJlbG93LiAtLWp3KgoKYGBge3J9Cm1tLjQgPC0gZ2xtZXIoY29ycmVjdCB+IGRlbGF5ICogbm9ybV9hZ2UgKwogICAgICAgICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgdHJpYWwgKwogICAgICAgICAgICAgICAgKDEgKyBkZWxheSB8IHNwZWNpZXMpCiAgICAgICAgICAgICAgLCBkYXRhID0gbW9kZWwuZGF0YQogICAgICAgICAgICAgICwgZmFtaWx5ID0gYmlub21pYWwKICAgICAgICAgICAgICAsIGNvbnRyb2wgPSBnbG1lckNvbnRyb2wob3B0aW1pemVyID0gImJvYnlxYSIsIG9wdEN0cmwgPSBsaXN0KG1heGZ1biA9IDJlNSkpCiAgICAgICAgKQpgYGAKCiMgTW9kZWwgY29tcGFyaXNvbgoKV2UncmUgbG9va2luZyBmb3IgdGhlIGxvd2VzdCBBSUMoYykgYXMgdGhlIG1vZGVsIHdpdGggdGhlICdiZXN0IGZpdCcgd2l0aCBhIHJlYXNvbmFibGUgbnVtYmVyIG9mIHBhcmFtZXRlcnMuIChUb28gbWFueSBhcmUgcGVuYWxpemVkIGJ5IEFJQyBhcyBvbmUgd2F5IHRvIGFkZHJlc3Mgb3ZlcmZpdHRpbmcuKQoKSW5kZWVkLCB0aGUgcmVkdWNlZCBtb2RlbCBzZWVtcyB0byBkbyBhIGJldHRlciBqb2Igb2Ygc3RyaWtpbmcgdGhhdCBiYWxhbmNlIGJldHdlZW4gZml0dGluZyB0aGUgZGF0YSB3aXRoIGZld2VyIHBhcmFtZXRlcnMuCgpgYGB7cn0KYmJtbGU6OkFJQ2N0YWIobW0uMSwgbW0uMiwgbW0uMywgbW0uNCkKYGBgCgpgYGB7cn0KYW5vdmEobW0uMSwgbW0uMiwgbW0uMywgbW0uNCkKYGBgCgojIyBEaWZmZXJlbmNlIGluIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzCgpEaWZmZXJlbmNlCgpgYGB7cn0KY29lZjEgPSBjb2VmKHN1bW1hcnkobW0uMSkpW2MoMiwzLDYpLCAxXQpjb2VmMiA9IGNvZWYoc3VtbWFyeShtbS4zKSlbYygyLDMsNiksIDFdCgpjb2VmMiAtIGNvZWYxCmBgYAoKRGlmZmVyZW5jZSBpbiBvZGRzIHJhdGlvcwoKYGBge3J9CmV4cChjb2VmMikgLSBleHAoY29lZjEpCmBgYAoK